Debugging Commands

When testing and debugging a work of interactive fiction it is sometimes useful to be able to make use of a set of speoial commands not available to normal players of your game. These special commands are known as debugging commands and should only be included in the debug build of your game, not the release build. This section describes the built-in debugging commands and briefly discusses how you might add your own.

Built-In Debugging Commands

The following debugging commands are included in the adv3Lite library. They are only included in your game when you compile for debugging, and not when you compile for release.

PURLOIN: The command PURLOIN FOO (which can be abbreviated to PN FOO) can be used to add any object to the player's inventory; e.g. PURLOIN BRASS KEY causes the brass key to jump magically into the player's possession from wherever the brass key is in the game world. The PURLOIN command does impose certain sanity checks, so that, for example, you can't purloin yourself, or a room, or anything that currently contains the player character, but you can purloin things that would normally be fixed in place.

GONEAR: The command GONEAR FOO (which can be abbreviated to GN FOO or spelt as GO NEAR FOO) teleports the player character to FOO. If FOO is a room the player character is taken to that room, otherwise the player character is taken to the Room that encloses FOO. If FOO is off-stage (i.e. if its location is nil) the command is not allowed to go ahead. If FOO is a multiloc the destination the player character is taken to may not be well defined.

FIAT LUX: The command FIAT LUX (or alternatively LET THERE BE LIGHT) causes the player character to light up so s/he can see in an otherwise dark place. Repeating the command toggles the illumination off again.

DEBUG: The command DEBUG simply breaks into the debugger.

DEBUG ACTIONS: The command DEBUG ACTIONS toggles the display of debugging information about actions on and off. The information displayed is simply the name of the action (e.g. PutOn) followed by the names of any objects, topics or literals involved in the action, separated by colons. The information is displayed at the start of the action execution cycle.

DEBUG DOERS: The command DEBUG DOERS toggles the display of debugging information about doers on and off. The information displayed is simply the cmd string of the Doer (e.g. 'put Thing in casket') . The information is displayed just before Doer.exec() is called.

DEBUG MESSAGES: The command DEBUG MESSAGES toggles the display of debugging information about messages on and off. The information displayed is the id and pre-processed string of any messages output via DMsg() or BMsg() that have a non-empty id. This may be helpful in identifying where the library is getting many of its default messages from.

DEBUG SPELLING: The command DEBUG SPELLING simply toggles the display of information about how long the spelling corrector took to make a correction.

DEBUG STOP or DEBUG OFF: These commands are synonymous and simply turn all the other debugging options (the three above) off at once.

DEBUG STATUS: the command DEBUG STATUS lists whether the various debugging options are currently on or off.

TEST XXX: the TEST command can be used to run test scripts, on which see further below.

EVAL: The command EVAL expression (where expression is any valid TADS 3 expression) evaluates the expression and displays the result. If the expression evaluates to an object EVAL diplays the name property of the object (if it has one) together with its superclass list. For example:

>eval 2 + 5
7

>eval me.location
hall [Room, ShuffledEventList]

>eval hall.contents
you [Thing],George [Actor],red ball [Thing],front door [Door],pictures [Thing],stairs [Thing],dial [NumberedDial],
  floor [MultiLoc,Decoration],ceiling [MultiLoc,Decoration]

>eval frontDoor.isLocked
true

>open front door
The front door is locked. 

>eval frontDoor.makeLocked(nil)
nil

>open front door
You open the front door.

The final example of EVAL above illustrates that it is perfectly possible to use the EVAL command with an expression that changes the game state.


Defining New Debugging Commands

If you want to define additional debugging commands for your particular game, you can do so using the same means as described in the section on Defining New Actions above, with a couple of extra steps:

  1. Enclose the complete definition between #ifdef __DEBUG and #endif preprocessor commands to ensure that your custom debugging commands are included only when you compiple for debugging.
  2. Ensure your action definition allows for universal scope if it needs to be able to act on any item in the game (and not just those that would normally be available to the player character).

For example, if in addition to the PURLOIN command which magically transports objects into the player character's inventory, we'd like to have a SUMMON command which can summon any object into the player character's presence (i.e. the same room as the player character) we could do it like this:

 
#ifdef __DEBUG

VerbRule(Summon)
    'summon' multiDobj
    : VerbProduction
    action = Summon
    verbPhrase = 'summon/summoning (what)'
    missingQ = 'what do you want to summon'
;

DefineTAction(Summon)
    addExtraScopeItems(role) { makeScopeUniversal(); }
    beforeAction() { }
    afterAction() { }
    turnSequence() { }
;

modify Thing
    dobjFor(Summon)
    {
        verify()
        {
            if(isIn(gActor.getOutermostRoom))
                illogicalNow('{The subj dobj} {is} already here. ' );
        }
        
        action()
        {
            moveInto(gActor.getOutermostRoom);
        }
        
        report()
        {
            "\^<<gActionListStr>> appears before you! ";
        }
    }
;
#endif

Note that since we probably don't want our debugging action to count as normal turn we override beforeAction(), afterAction() and turnSequence() on Summon to do nothing, thereby suppressing the before and after notifications, daemons and advancing the turn count.(I'm assuming that since this is a debugging command the fact that 'appears' may not agree with its subject is not too much of problem, but if it bothers you correcting it is left as an exercise for the reader.)


Basic Test Scripts

For the full story on Test Scripts see on Enhanced Test Scripts below. In this section we shall just cover the basics.

When testing a game it's often useful to be able to run a set of commands to test a particular feature. In adv3Lite you can set up named test scripts to help automate this task. To define a test script, define an object of the Test class (it can be an anonymous object). Use the testName property to give the test script a name by which it can be referred to with a TEST command. Then define the list of commands to be performed by the test script in the testList property. For example:

 Test 
    testName = 'foo'
    testList = ['x me', 'i', 'look']
 ; 
 

With this definition in place you can use the command TEST FOO to carry out X ME followed by I followed by LOOK. You can also abbreviate the definition of the Test object by using the built-in template, so:

 
 Test 'foo' ['x me', 'i', 'look'];

For some scripts to work as required it may be necessary for the actor to be in a particular location, or to have particular items in his/her inventory. For example, to have the player character unlock the golden door with the diamond key and then enter the throne room of the mystic queen, you may first need to move the player character to the tulip passage and ensure that the right key is in his inventory. The player character may also need to have the yellow rose in his inventory if he is then to present it to the mystic queen. We could set up these conditions by using gonear and purloin commands in the script, but we can also do so using the location and testHolding properties of our Test object, thus:

Test testName = 'queen' testList = ['unlock golden door with diamond key', 'n', 'x queen', 'give yellow rose to queen'] location = tulipPassage testHolding = [diamondKey, yellowRose] ;

Again, this may be abbreviated via use of the template to:

Test 'queen' ['unlock golden door with diamond key', 'n', 'x queen', 'give yellow rose to queen']
  @tulipPassage [diamondKey, yellowRose]
;

The command TEST QUEEN will then move the player character to tulipPassage, move the diamondKey and the yellowRose into his inventory, and then execute the defined sequence of commands. By default a room description will be displayed following the move, but if you wish you can disable this by setting the reportMove property of the Test to nil (it's true by default to remind you of the effect of the TEST command). Similarly, by default the TEST command will notify you of items it moves into the player character's inventory, but this notification can be suppressed by setting the reportHolding property to nil.

You can remind yourself of what Tests you've defined in your game by using the LIST TESTS command. The command LIST TESTS FULLY also shows the list of commands associated with each test name.

Finally, the TEST class and its associated actions are only defined in a debug build, so you need to make sure that you surround any TEST definitions with #ifdef _DEBUG and #endif so that they don't cause compilation errors in a release build; for example:

#ifdef __DEBUG

Test 'foo' ['x me', 'i', 'look'];

Test 'queen' ['unlock golden door with diamond key', 'n', 'x queen', 'give yellow rose to queen']
  @tulipPassage [diamondKey, yellowRose]
;
 
#endif 
 

Enhanced Test Scripts (with Assertions)

Specification

Thanks to work by Mitchell Mlinar, the Basic Test Script functionality described above has now been substantially enchanced, not least to incorporate assertion-based testing. This now supports the following features:

Test definition

Using one of two templates you will want to add somewhere (see templates.h), the primary definition for NewTest extends upon text: NewTest 'testName' ['cmd1','cmd2',...] [list of objects to purloin] @location where both the [list of objects to purloin] and @location (where the player character is moved first) are optional. Here is where the similarity ends. There are five flags for controlling how the test will run including two that were there before:

Assert-based test commands

All of these commands are intended to be inserted into your test command list and validate some condition. If the condition is true, the test continues. If the condition is false, a report is made to the console and that test fails and will not execute any more commands. (This is where the value of restoring game state or even restarting the game are important for testing.) Whether subsequent tests are executed or not depends upon the testall <option>. (NOTE: None of these commands advance the game time/counter but are instead considered system commands.)

  • assertMsg text: Succeeds if text is shown in the most recent message(s) printed to the console. This is usually some phrase that identifies some unique message you expect to see. This is substring match meaning that if text phrase appears anywhere in prior messages, it will succeed the assertMsg. Note that the way this works is to keep a message buffer where this search is actually performed and is, by default, cleared after each non-assert command, so only the last actual command's message(s) will be checked. (You can change this setting but it is not recommended.) This is one of the most powerful commands for assertion-based testing as you can validate what the user is seeing on the screen. Example: assertMsg jumped over the rock.
  • assertMsgClear: This command clears the message buffer. Should only be needed if you set clearAssertBufferBeforeCmd = nil. It is a test no-op.
  • assert "expression": This is the second most powerful command in the assertion-based test command set. Leveraging eval, this tests a game-code expression and, if the expression is nil, the test fails. All other values are considered a success. Note that you will be using object and room references that are part of your game code, not the user-visible name. So, for example, oroom: Room 'outdoor room' is referred to as oroom NOT outdoor room. Example: assert me.isIn(bedquilt). Finally, the quotes are usually optional but there is one situation where the expression will result in a game error without quotes and that is when you want to test the inverse as in assert !ring.isWornBy(me). Here, you instead must use quotes: assert "!ring.isWornBy(me)";
  • assertPlayerInRoom room Name: Succeeds if the player character is in the named room. Example: assertPlayerInRoom long hallway.
  • assertPlayerHasItem item Name: Succeeds if the player character carries the specified item. Only one item is allowed per line. Example: assertPlayerHasItem red ball.
  • assertPlayerLacksItem item Name: Succeeds if the player character is NOT carrying the specified item. Only one item is allowed per line. Example: assertPlayerLacksItem blue ball.
  • assertPlayerRoomHasItem item Name: Succeeds if the room the player character is located contains the specified item. Only one item is allowed per line. Example: assertPlayerRoomHasItem red ball.
  • assertPlayerRoomLacksItem item Name: Succeeds if room the player character is located does NOT contain the specified item. Only one item is allowed per line. Example: assertPlayerRoomLacksItem blue ball.
  • To use any of these assertXXX commands you simply include them in your Test definition like any other commands it may include, e.g.:

    
    Test 'test1' ['east', 'get red ball', 'assertPlayerHasItem red ball'] ;
    

    How to run your test(s)

    There are several commands for running your tests:

  • test testname: Run the named test.
  • list tests [fully|sorted]: Lists the tests. By default (or if sorted), this will also include the flag settings of the test. Whereas normally the tests are listed in the order found during compilation, the sorted option sorts them by name.
  • testall [nostop]: Run all of the tests (in the order found during compilation). By default, this will stop running when any test fails (that is caused by any assertion failing). This allows you to quickly address the problem. If you use the nostop option, it will run all of the tests whether they fail or not. In the end, a summary of the results is printed to console as shown in the example below:
  • ===========================
    ===========================
    Total tests:     16
    Total asserts:  26
    Failed asserts: 0
    ===========================
    

    Finally, don't forget to enclose your Test definitions in a #ifdef __DEBUG ... #endif block, e.g.:

    
    #ifdef __DEBUG
    Test 'test1' ['east', 'get red ball', 'assertPlayerHasItem red ball', 'jump'] 
    ;
    
    
    Test 'test2' ['drop red ball', 'throw red ball at blue ball', 'get balls', 'i',
        'assert "blueBall.isIn(me)"'] [bugle]
        restoreStartStateAfterTest =  true   
    ;
    
    Test 'test3' ['i', 's','t me', 
        'assertMsg i am someone'] @startroom
        restartBeforeTest = true   
    ;
    
    Test 'test123' ['test test1', 'test test2', 'test test3'];
    
    #endif
    
    

    Additional Debugging Resources

    The adv3Library has one or two built-in checks to help with various kinds of common situations. For example, when writing conversational responses it's very easy to mismatch the smart quote tags <q> and </q>; the adv3Lite thus looks out for any smart quotes that are mismatched over the course of a single turn and displays a warning whenever mismatched smart quotes are displayed, so the game author can correct them (it also tries to prevent the effect of mismatched smart quotes propagating to successive turns). If you want to suppress the warning messages in the released version of your game you can override quoteFilter.showWarnings to nil; however, you might find it useful to leave the warnings on for versions of the game you sent to beta-testers, which is why this isn't simply tied to whether or not you compiled for debugging.

    When your beta-testers test your game, it is often helpful to get them to record a transcript of it (using the SCRIPT command), in the course of which they can type comments such as:

     >* TYPO elehpant -> elephant
     ...
     >* BUG! The brass key won't work on the inside of the tower door
     

    By default such comments are commands that start with an asterisk (*). To change how comments should be prefixed, see the discussion of the commentPreParser.